#include <iostream>
#include <list>
#include <stdlib.h>
#include "iris/genericTopology/genericTopoCreator.h"
#include "iris/interfaces/genericIrisInterface.h"
#include "kernel/manifold.h"
#include "kernel/component.h"

using namespace std;

using namespace manifold::kernel;
using namespace manifold::iris;


// This class defines data sent between terminals.
const unsigned DATASIZE = 8;

class TerminalData {
public:
    unsigned src; //ID of source terminal
    unsigned dst; //ID of destination terminal
    unsigned data[DATASIZE];  //do not use containers
    
    unsigned get_src() { return src; }
    unsigned get_dst() { return dst; }
};


//This class emulates a terminal.
class Mockterminal : public manifold::kernel::Component {
public:
    enum {PKTOUT=0};
    enum {PKTIN=0};

    Mockterminal(unsigned id, unsigned n_nodes, unsigned total_pkts) :
        m_id(id), m_NODES(n_nodes), m_TOTAL_PKTS(total_pkts)
    {   
        m_pkt_count = 0;
    }
    
    void tick ()
    {  
	if(m_pkt_count < m_TOTAL_PKTS) {
	//cout << "Terminal " << m_id << " sending\n";
	    TerminalData* td = new TerminalData;
	    td->src = m_id;

	    while((td->dst = random() % m_NODES) == m_id); //randomly pick a destination, but not self.

	    Send(PKTOUT , td);
            m_pkt_count++;   
        } 
    }
    
    void tock()
    {   
        //do nothing
    }
    
    void handle_incoming_pkt(int, TerminalData* td)
    {
        m_pkts.push_back (*td);
    }    

private:
    unsigned m_id;
    unsigned m_pkt_count; //number of pkts that have been sent
    const unsigned m_NODES; //number of nodes in the network
    const unsigned m_TOTAL_PKTS; //pkts to send to each of the other nodes

    list<TerminalData> m_pkts; //received pkts

};



const int MASTER_CLOCK_HZ = 1000;
Clock MasterClock(MASTER_CLOCK_HZ);


int main(int argc, char** argv)
{

    //check command-line arguments
    if(argc != 3) {
        cerr << "Usage: " << argv[0] << " <num_nodes>  <num_packets>" << endl
	     << "   num_nodes: number of nodes in the network." << endl
	     << "   num_packets: max number of packets a node can send." << endl;
	exit(1);
    }

    const unsigned NUM_NODES = atoi(argv[1]);
    if(NUM_NODES < 2 || NUM_NODES > 256) {
        cerr << "Number of nodes must be between 2 and 256." << endl;
	exit(1);
    }

    const unsigned NUM_PKTS = atoi(argv[2]);
    if(NUM_PKTS < 1) {
        cerr << "Number of packets cannot be less than 1." << endl;
	exit(1);
    }


    Manifold :: Init(argc, argv);

    //the paramters for ring network
    ring_init_params rp;
    rp.no_nodes = NUM_NODES;
    rp.no_vcs = 1; //number of virtual channels //4
    rp.credits = 100; //number of credits for each virtual channel //3
    rp.rc_method = RING_ROUTING;
    rp.link_width = 128;
        
    Clock& clk = MasterClock;
        
    //creat the ring network
    Simple_terminal_to_net_mapping* mapping = new Simple_terminal_to_net_mapping();
    Ring<TerminalData>* myRing = topoCreator<TerminalData>::create_ring(clk, &rp, mapping, 0, 0);

        
    //creat the terminals
    vector<CompId_t> term_ids(NUM_NODES);        
    vector<Mockterminal*> terms(NUM_NODES); //pointer to terminal objects

    for (unsigned i=0; i<NUM_NODES; i++) {
	term_ids[i] = Component :: Create<Mockterminal> (0, i, NUM_NODES, NUM_PKTS);
	terms[i] = Component :: GetComponent<Mockterminal>(term_ids[i]);
	if (terms[i])
	    Clock::Register(clk, terms[i], &Mockterminal::tick, &Mockterminal::tock);
    }
        
    //get the network interfaces' component IDs for connection.
    const vector <manifold::kernel::CompId_t>& inf_ids = myRing->get_interface_id();

    //connect terminal and interface
    const Ticks_t LATENCY = 1;
    for (unsigned i=0; i<NUM_NODES; i++) {
	Manifold::Connect(term_ids[i], Mockterminal::PKTOUT, inf_ids[i], 
		      GenNetworkInterface<TerminalData>::DATAFROMTERMINAL,
		      &GenNetworkInterface<TerminalData>::handle_new_packet_event, LATENCY);
	Manifold::Connect(inf_ids[i], GenNetworkInterface<TerminalData>::DATATOTERMINAL, term_ids[i], 
		      Mockterminal::PKTIN, &Mockterminal::handle_incoming_pkt, LATENCY);               
    }
        
    Manifold::StopAt(10000);
    Manifold::Run();
        
    myRing->print_stats(cout);

    Manifold :: Finalize();

}


